/*
 * ImageAnimView.cpp
 *
 *  Created on: 2024年8月17日
 *      Author: ZKSWE Develop Team
 */

#include "ImageAnimView.h"
#include "zlib.h"
#include "utils/Log.h"

namespace ui {

#define REGION_WIDTH(rc)                    ((rc).right - (rc).left)
#define REGION_HEIGHT(rc)                   ((rc).bottom - (rc).top)

#define REGION_SET(rc, l, t, r, b)          \
    do {                                    \
        (rc).left = l;                     \
        (rc).top = t;                      \
        (rc).right = r;                    \
        (rc).bottom = b;                   \
    } while (0)

#define REGION_IS_EMPTY(rc)                \
    (((rc).left == (rc).right) ||          \
     ((rc).top == (rc).bottom))

#define QOI_OP_INDEX  0x00 /* 00xxxxxx */
#define QOI_OP_DIFF   0x40 /* 01xxxxxx */
#define QOI_OP_LUMA   0x80 /* 10xxxxxx */
#define QOI_OP_RUN    0xc0 /* 11xxxxxx */
#define QOI_OP_RGB    0xfe /* 11111110 */
#define QOI_OP_RGBA   0xff /* 11111111 */
#define QOI_MASK_2    0xc0 /* 11000000 */

#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
#define QOI_MAGIC \
	(((uint32_t)'q') << 24 | ((uint32_t)'o') << 16 | \
	 ((uint32_t)'i') <<  8 | ((uint32_t)'f'))
#define QOI_HEADER_SIZE 14

#define QOI_PIXELS_MAX ((uint32_t)400000000)

typedef struct {
	uint32_t width;
	uint32_t height;
	uint8_t channels;
	uint8_t colorspace;
} qoi_desc;

typedef union {
	struct { uint8_t r, g, b, a; } rgba;
	uint32_t v;
} qoi_rgba_t;

typedef struct {
	char magic[8];
	uint32_t uncompr_size;
} bin_head_t;

typedef struct {
	char magic[8];
	uint16_t w;
	uint16_t h;
	uint16_t count;
	uint8_t reserve[2];
} region_head_t;

static const uint8_t qoi_padding[8] = {0,0,0,0,0,0,0,1};

static uint32_t _qoi_read_32(const uint8_t *bytes, int *p) {
	uint32_t a = bytes[(*p)++];
	uint32_t b = bytes[(*p)++];
	uint32_t c = bytes[(*p)++];
	uint32_t d = bytes[(*p)++];
	return a << 24 | b << 16 | c << 8 | d;
}

static bool _qoi_decode(const void *data, int size, bitmap_t &bmp) {
	const uint8_t *bytes;
	uint32_t header_magic;
	uint32_t *pixels;
	qoi_rgba_t index[64] = { 0 };
	qoi_rgba_t px;
	qoi_desc desc;
	int px_len, chunks_len, px_pos;
	int p = 0, run = 0;

	if (!data || (size < QOI_HEADER_SIZE + (int) sizeof(qoi_padding))) {
		return false;
	}

	bytes = (const uint8_t *) data;

	header_magic = _qoi_read_32(bytes, &p);
	desc.width = _qoi_read_32(bytes, &p);
	desc.height = _qoi_read_32(bytes, &p);
	desc.channels = bytes[p++];
	desc.colorspace = bytes[p++];

	if (
		desc.width == 0 || desc.height == 0 ||
		desc.channels < 3 || desc.channels > 4 ||
		desc.colorspace > 1 ||
		header_magic != QOI_MAGIC ||
		desc.height >= QOI_PIXELS_MAX / desc.width
	) {
		return false;
	}

	if ((desc.width != bmp.width) || (desc.height != bmp.height)) {
		LOGE("qoi w or h not equal\n");
		return false;
	}

	px_len = desc.width * desc.height;
	pixels = (uint32_t *) bmp.data;

	px.rgba.r = 0;
	px.rgba.g = 0;
	px.rgba.b = 0;
	px.rgba.a = 255;

	chunks_len = size - (int) sizeof(qoi_padding);
	for (px_pos = 0; px_pos < px_len; px_pos++) {
		if (run > 0) {
			run--;
		} else if (p < chunks_len) {
			int b1 = bytes[p++];

			if (b1 == QOI_OP_RGB) {
				px.rgba.r = bytes[p++];
				px.rgba.g = bytes[p++];
				px.rgba.b = bytes[p++];
			} else if (b1 == QOI_OP_RGBA) {
				px.rgba.r = bytes[p++];
				px.rgba.g = bytes[p++];
				px.rgba.b = bytes[p++];
				px.rgba.a = bytes[p++];
			} else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
				px = index[b1];
			} else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
				px.rgba.r += ((b1 >> 4) & 0x03) - 2;
				px.rgba.g += ((b1 >> 2) & 0x03) - 2;
				px.rgba.b += ( b1       & 0x03) - 2;
			} else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
				int b2 = bytes[p++];
				int vg = (b1 & 0x3f) - 32;
				px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
				px.rgba.g += vg;
				px.rgba.b += vg - 8 +  (b2       & 0x0f);
			} else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
				run = (b1 & 0x3f);
			}

			index[QOI_COLOR_HASH(px) % 64] = px;
		}

		pixels[px_pos] = px.v;
	}

	return true;
}

static void* _load_file_data(const char *file, uint32_t &size) {
	FILE *pf = fopen(file, "rb");
	if (!pf) {
		return NULL;
	}

	void *data = NULL;
	do {
		fseek(pf, 0, SEEK_END);
		size = ftell(pf);
		fseek(pf, 0, SEEK_SET);

		data = malloc(size);
		if (!data) {
			break;
		}

		if (fread(data, size, 1, pf) != 1) {
			free(data);
			data = NULL;
		}
	} while (0);
	fclose(pf);

	return data;
}

static bool _decode_bin(const std::string &bin, bitmap_t &bmp) {
	uint32_t file_size = 0;
	void *file_data = _load_file_data(bin.c_str(), file_size);
	if (!file_data) {
		LOGE("load %s fail\n", bin.c_str());
		return false;
	}

	bool ret = false;

	do {
		if (file_size < sizeof(bin_head_t)) {
			break;
		}

		bin_head_t *head = (bin_head_t *) file_data;
		if (strcmp(head->magic, "ZKBIN") != 0) {
			LOGE("bin magic err\n");
			break;
		}

		z_stream d_stream = { 0 };
		if (inflateInit(&d_stream) != Z_OK) {
			LOGE("inflateInit err\n");
			break;
		}

		uint8_t *out = (uint8_t *) malloc(head->uncompr_size);

		d_stream.next_in = (uint8_t *) file_data + sizeof(bin_head_t);
		d_stream.avail_in = file_size - sizeof(bin_head_t);
		d_stream.next_out = out;
		d_stream.avail_out = head->uncompr_size;

		if (inflate(&d_stream, Z_NO_FLUSH) == Z_STREAM_END) {
			if (inflateEnd(&d_stream) == Z_OK) {
				ret = _qoi_decode(out, d_stream.total_out, bmp);
			} else {
				LOGE("inflateEnd err\n");
			}
		} else {
			LOGE("inflate err\n");
		}

		free(out);
	} while (0);
	free(file_data);

	return ret;
}

static bitmap_t* _load_bmp(int w, int h, int uuid) {
	char file[64];
	sprintf(file, "/tmp/_tmp_%d.bmp", uuid);

	FILE *fp = fopen(file, "wb");
	if (!fp) {
		return NULL;
	}

	int pitch = (w * 3 + 3) / 4 * 4;
	int size = pitch * h;
	int bmi[] = { size + 54, 0, 54, 40, w, h, 1 | (24 << 16), 0, size, 0, 0, 0, 0 };

	fprintf(fp, "BM");
	fwrite(&bmi, sizeof(bmi), 1, fp);
	fclose(fp);

	bitmap_t *bmp = NULL;
	BitmapHelper::loadBitmapFromFile(bmp, file);
	remove(file);

	if (bmp && bmp->data) {
		memset(bmp->data, 0, bmp->pitch * bmp->height);
	}

	return bmp;
}

static bool _decode_region_bin(const std::string &bin, region_head_t &head, region_t *&list, uint32_t &count) {
	uint32_t file_size = 0;
	void *file_data = _load_file_data(bin.c_str(), file_size);
	if (!file_data) {
		LOGE("load %s fail\n", bin.c_str());
		return false;
	}

	bool ret = false;

	do {
		if (file_size < sizeof(region_head_t)) {
			break;
		}

		region_head_t *region_head = (region_head_t *) file_data;
		if (strcmp(region_head->magic, "ZKREG") != 0) {
			LOGE("region bin magic err\n");
			break;
		}

		uint32_t region_list_size = region_head->count * sizeof(region_t);
		if (region_list_size < file_size - sizeof(region_head_t)) {
			break;
		}

		head = *region_head;

		count = region_head->count;
		if (count > 0) {
			list = (region_t *) malloc(region_list_size);
			memcpy(list, region_head + 1, region_list_size);
		} else {
			list = NULL;
		}

		ret = true;
	} while (0);

	free(file_data);

	return ret;
}

static inline void _region_bound(region_t &rc, const region_t &rc1, const region_t &rc2) {
	if (REGION_IS_EMPTY(rc1)) {
		rc = rc2;
	} else if (REGION_IS_EMPTY(rc2)) {
		rc = rc1;
	} else {
		REGION_SET(rc, std::min(rc1.left, rc2.left), std::min(rc1.top, rc2.top),
				std::max(rc1.right, rc2.right), std::max(rc1.bottom, rc2.bottom));
	}
}


ImageAnimView::ImageAnimView(ZKBase *parent, int id, const LayoutPosition &pos) :
		ZKBase(parent),
		canvas_bmp_(NULL),
		region_list_(NULL),
		list_count_(0),
		play_index_(-1) {
	Json::Value json;
	json["id"] = id;
	create(json);
	setPosition(pos);

	memset(&img_bmp_, 0, sizeof(bitmap_t));
	REGION_SET(dirty_, 0, 0, 0, 0);

	run();
}

ImageAnimView::~ImageAnimView() {
	Message msg;
	msg.mMsgType = E_MSG_EXIT;
	msg_queue_.queueMessage(&msg);
	requestExitAndWait();

	release();
}

bool ImageAnimView::load(const std::string &dir) {
	Mutex::Autolock _l(lock_);
	release();

	region_head_t head;
	region_t *region_list = NULL;
	uint32_t list_count = 0;

	if (!_decode_region_bin(dir + "/region.bin", head, region_list, list_count)) {
		LOGE("decode region bin error\n");
		return false;
	}

	canvas_bmp_ = _load_bmp(head.w, head.h, (int) this);
	if (!canvas_bmp_) {
		free(region_list);
		return false;
	}

	setBackgroundBmp(canvas_bmp_);

	img_bmp_.width = canvas_bmp_->width;
	img_bmp_.height = canvas_bmp_->height;
	img_bmp_.pitch = canvas_bmp_->pitch;
	img_bmp_.data = (uint8_t *) malloc(img_bmp_.pitch * img_bmp_.height);

	region_list_ = region_list;
	list_count_ = list_count;
	dir_ = dir;

	return true;
}

void ImageAnimView::play(int index, bool sync) {
	if (sync) {
		procPlay(index);
	} else {
		Message msg;
		msg.mMsgType = E_MSG_PLAY;
		msg.mPara0 = index;
		msg_queue_.flushMessage();
		msg_queue_.queueMessage(&msg);
	}
}

void ImageAnimView::procPlay(int index) {
	Mutex::Autolock _l(lock_);

	// play list count = region list count + 1
	if ((index < 0) || (index > (int)list_count_)) {
		LOGE("proc play invalid index %d\n", index);
		return;
	}

	if (play_index_ == index) {
		return;
	}

	char path[256];
	sprintf(path, "%s/%d.bin", dir_.c_str(), index);

	if (_decode_bin(path, img_bmp_)) {
#if 1
		if(play_index_ == -1){
			REGION_SET(dirty_, 0, 0, img_bmp_.width, img_bmp_.height);
		}else{
			int min, max;
			if (play_index_ > index) {
				min = index;
				max = play_index_;
			} else {
				min = play_index_;
				max = index;
			}

			for (int i = min; i < max; ++i) {
				_region_bound(dirty_, dirty_, region_list_[i]);
			}
		}
		LayoutPosition pos(dirty_.left, dirty_.top, REGION_WIDTH(dirty_), REGION_HEIGHT(dirty_));
		invalidate(&pos);
#else
		REGION_SET(dirty_, 0, 0, img_bmp_.width, img_bmp_.height);
		setInvalid(!isInvalid());
#endif
	}

	play_index_ = index;
}

void ImageAnimView::release() {
	// 不需要销毁bmp，由控件内部销毁
	setBackgroundBmp(NULL);
	canvas_bmp_ = NULL;

	free(img_bmp_.data);
	memset(&img_bmp_, 0, sizeof(bitmap_t));

	free(region_list_);
	region_list_ = NULL;
	list_count_ = 0;

	REGION_SET(dirty_, 0, 0, 0, 0);
	play_index_ = -1;
}

void ImageAnimView::onDraw(ZKCanvas *pCanvas) {
	if (!REGION_IS_EMPTY(dirty_)) {
		Mutex::Autolock _l(lock_);
		if (canvas_bmp_ && canvas_bmp_->data && img_bmp_.data) {
			//LOGD("update bmp data start\n");

			uint32_t x = dirty_.left;
			uint32_t y = dirty_.top;
			uint32_t w = REGION_WIDTH(dirty_);
			uint32_t h = REGION_HEIGHT(dirty_);

			if ((x == 0) && (y == 0) && (w == canvas_bmp_->width) && (h == canvas_bmp_->height)) {
				memcpy(canvas_bmp_->data, img_bmp_.data, canvas_bmp_->pitch * canvas_bmp_->height);
			} else {
				uint8_t *src = img_bmp_.data + y * img_bmp_.pitch + x * 4;
				uint8_t *dst = canvas_bmp_->data + y * canvas_bmp_->pitch + x * 4;
				for (uint32_t i = 0; i < h; ++i) {
					memcpy(dst, src, w * 4);
					src += img_bmp_.pitch;
					dst += canvas_bmp_->pitch;
				}
			}

			//LOGD("update bmp data end\n");
		}

		REGION_SET(dirty_, 0, 0, 0, 0);
	}

	return ZKBase::onDraw(pCanvas);
}

bool ImageAnimView::threadLoop() {
	bool exit = false;
	while (!exit && !exitPending()) {
		Message msg;
		if (!msg_queue_.dequeueMessage(&msg)) {
			msg_queue_.waitMessage();
			continue;
		}

		switch (msg.mMsgType) {
		case E_MSG_PLAY:
			procPlay(msg.mPara0);
			break;

		case E_MSG_EXIT:
			exit = true;
			break;
		}
	}

	LOGD("image anim view thread exit...\n");

	return false;
}

}
